package com.chentong.utils;

import com.chentong.annotation.*;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultAttribute;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ApplicationContext {
    private static final Map<String, Object> IOC_MAP = new ConcurrentHashMap<>();
    private static final List<String> CLASS_NAME_LIST = new ArrayList<>();
    private static final Map<String, Method> HANDLER_MAP = new ConcurrentHashMap<>();

    public Map<String, Object> getApplicationContext() {
        return IOC_MAP;
    }

    public List<String> getClassNameByBasePackage() {
        return CLASS_NAME_LIST;
    }

    public Map<String, Method> getRegisteredHandler() {
        return HANDLER_MAP;
    }

    public void init(String xmlPath) throws Exception {
        scanPackage(saxBasePackage(xmlPath));
        reflectToInstance();
        HandlerMapping();
        initApplicationContextByXML(xmlPath);
    }

    private void HandlerMapping() throws Exception {
        for (String className : CLASS_NAME_LIST) {
            Class<?> clazz = Class.forName(className);
            if (clazz.getAnnotation(Controller.class) != null &&
                    clazz.getAnnotation(RequestMapping.class) != null) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                String baseUrl = requestMapping.url();
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping request = declaredMethod.getAnnotation(RequestMapping.class);
                        HANDLER_MAP.put(baseUrl + request.url(), declaredMethod);
                    }
                }
            }
        }
    }

    private String saxBasePackage(String xmlPath) throws Exception {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(ApplicationContext.class.getClassLoader().getResource(xmlPath));
        Element rootElement = document.getRootElement();
        Element basePackageElement = rootElement.element("package-scan");
        return basePackageElement.attributeValue("basePackage");
    }

    private void scanPackage(String basePackagePath) {
        String basePackage = basePackagePath.replaceAll("\\.", "/");
        URL url = ApplicationContext.class.getClassLoader().getResource(basePackage);
        File baseDir = new File(url.getFile());
        File[] files = baseDir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                CLASS_NAME_LIST.add(basePackagePath + "." + file.getName().replaceAll("\\.class", ""));
            } else {
                scanPackage(basePackagePath + "." + file.getName());
            }
        }
    }

    private void reflectToInstance() throws Exception {
        for (String className : CLASS_NAME_LIST) {
            Class clazz = Class.forName(className);

            if (clazz.getAnnotation(Controller.class) != null ||
                    clazz.getAnnotation(Component.class) != null ||
                    clazz.getAnnotation(Repository.class) != null ||
                    clazz.getAnnotation(Service.class) != null) {
                Object object = Class.forName(className).newInstance();
                String simpleName = ClassNameUtils.humpNaming(Class.forName(className).getSimpleName());
                IOC_MAP.put(simpleName, object);
            }
        }
        DI();
    }

    private void DI() throws Exception {
        IOC_MAP.forEach((beanName, objectBean) -> {
            try {
                Class clazz = objectBean.getClass();
                List<Field> fieldList = Arrays.asList(clazz.getDeclaredFields());
                for (Field field : fieldList) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        field.setAccessible(true);
                        String value = field.getAnnotation(Autowired.class).value();
                        if (value.equals("")) {
                            value = field.getName();
                        }
                        Object objectAttribute = IOC_MAP.get(value);
                        field.set(objectBean, objectAttribute);
                    } else if (field.isAnnotationPresent(Value.class)) {
                        field.setAccessible(true);
                        String value = field.getAnnotation(Value.class).value();
                        field.set(objectBean, value);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private void initApplicationContextByXML(String xmlPath) throws Exception {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(new File(xmlPath));

        Element rootElement = document.getRootElement();

        List beanList = rootElement.elements("bean");
        for (Object beanObject : beanList) {
            Element beanElement = (Element) beanObject;

            Attribute nameAttribute = beanElement.attribute("name");
            Attribute classAttribute = beanElement.attribute("class");

            List propertyList = beanElement.elements("property");
            Object objBean = IOC_MAP.get(nameAttribute.getValue());

            if (objBean == null) {
                objBean = Class.forName(classAttribute.getValue()).newInstance();
            }

            for (Object propertyObject : propertyList) {
                Element propertyElement = (Element) propertyObject;

                Attribute nameProperty = propertyElement.attribute("name");
                Attribute valueProperty = propertyElement.attribute("value");
                Attribute beanProperty = propertyElement.attribute("bean");

                if (valueProperty != null) {
                    //运用反射，通过类名反射到类对象，再通过属性名将类对象反射到当前属性
                    Class<?> beanClass = Class.forName(classAttribute.getValue());
                    //根据属性名获取当前属性
                    Field field = beanClass.getDeclaredField(nameProperty.getValue());

                    field.setAccessible(true);  //设置私有属性访问
                    field.set(objBean, valueProperty.getValue());
                } else {
                    //创建引用类型属性的Object对象
                    DefaultAttribute reflectClass = (DefaultAttribute) rootElement.selectSingleNode("/beans/bean[@name='" + beanProperty.getValue() + "']/@class");
                    Object quoteObject = IOC_MAP.get(reflectClass.getValue());

                    if (quoteObject == null) {
                        quoteObject = Class.forName(reflectClass.getValue()).newInstance();
                        IOC_MAP.put(beanProperty.getValue(), quoteObject);
                    }

                    //创建类对象
                    Class<?> quoteClass = Class.forName(classAttribute.getValue());
                    Field field = quoteClass.getDeclaredField(beanProperty.getValue());
                    field.setAccessible(true);
                    field.set(objBean, quoteObject);
                }
            }
            IOC_MAP.put(nameAttribute.getValue(), objBean);
        }
    }
}
